package com.briup.pai.common.aspect;

import cn.hutool.core.util.DesensitizedUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson2.JSON;
import com.briup.pai.common.enums.LogTypeEnum;
import com.briup.pai.common.enums.RequestStatusEnum;
import com.briup.pai.common.enums.ResultCodeEnum;
import com.briup.pai.common.exception.BriupAssert;
import com.briup.pai.common.result.Result;
import com.briup.pai.common.utils.IpUtil;
import com.briup.pai.common.utils.SecurityUtil;
import com.briup.pai.entity.dto.LoginWithPhoneDTO;
import com.briup.pai.entity.dto.LoginWithUsernameDTO;
import com.briup.pai.entity.po.Log;
import com.briup.pai.entity.po.User;
import com.briup.pai.service.ILogService;
import com.briup.pai.service.impl.UserServiceImpl;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.util.ArrayList;
import java.util.Date;

/**
 * 记录日志的切面类
 */
@Aspect
@Component
@Slf4j
public class LogAspect {

    @Autowired
    private HttpServletRequest request;

    @Autowired
    private UserServiceImpl userServiceImpl;

    @Autowired
    private ILogService logService;

    // 定义pointcut，controller层除了get方法外，其他方法都记录日志
    @Pointcut("execution(* com.briup.pai.controller.*.*(..)) && !execution(* com.briup.pai.controller.*.get*(..))")
    public void logPointcut() {
    }

    // 定义advice，环绕通知
    @Around("logPointcut()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
//        Result result;
        // 准备存入数据库的日志对象
        Log savelog = new Log();
        // 记录开始时间
        long startTime = System.currentTimeMillis();
        // 记录当前日志的时间
        savelog.setOperateTime(new Date());
        // 获取请求路径
        String requestURI = request.getRequestURI();
        savelog.setRequestUri(requestURI);
        // 获取请求方法
        String method = request.getMethod();
        savelog.setRequestMethod(method);
        // 获取解析后的ip地址
        String ipAddress = IpUtil.getIpAddress(request);
        savelog.setRequestIp(ipAddress);
        // 加入方法名字（从签名里面）
        savelog.setMethodName(joinPoint.getSignature().getName());
        // 记录日志
        log.info("【日志模块】：本次请求的URI为：{}，请求的方法名称为：{}", requestURI, method);


        // 判断日志操作的类型，分为登陆日志和操作日志
        if (requestURI.contains("login")) {
            savelog.setType(LogTypeEnum.LOGIN.getType());
            // 如果是登陆日志，需要对两种登陆方式进行参数的解析
            // 获取请求参数
            Object[] args = joinPoint.getArgs();
            // 判断args不为空
            if (ObjectUtil.isNotEmpty(args)) {
                // 携带了参数？代表不是登出
                // 取出第一个参数
                Object arg = joinPoint.getArgs()[0];
                // 根据arg来判断是账密登陆，还是验证码登陆
                if (arg instanceof LoginWithUsernameDTO dto) {
                    // 因为需要在数据中将密码隐藏，需要做一个对象的转换
                    LoginWithUsernameDTO loginWithUsernameDTO = new LoginWithUsernameDTO();
                    loginWithUsernameDTO.setUsername(dto.getUsername());
                    loginWithUsernameDTO.setPassword(DesensitizedUtil.password(dto.getPassword()));
                    // 将参数转换为json格式的字符串存放到saveLog中
                    savelog.setRequestParams(JSON.toJSONString(loginWithUsernameDTO));
                    // 将操作的用户名字放进去
                    savelog.setOperateUser(dto.getUsername());
                    log.info("【日志模块-登录日志-用户名密码登录】：本次请求的参数为{}",JSON.toJSONString(loginWithUsernameDTO));
                }else if (arg instanceof LoginWithPhoneDTO dto){
                    LoginWithPhoneDTO loginWithPhoneDTO = new LoginWithPhoneDTO();
                    // 使用手机号默认的脱敏方法133****8899
                    loginWithPhoneDTO.setTelephone(DesensitizedUtil.mobilePhone(dto.getTelephone()));
                    loginWithPhoneDTO.setCode(dto.getCode());
                    // 放用户名字看你是选择手机号还是用户名，这里用用户的名字
                    User user = BriupAssert.requireNotNull(userServiceImpl,
                            User::getTelephone,
                            dto.getTelephone(),
                            ResultCodeEnum.USER_NOT_EXIST);
                    savelog.setOperateUser(user.getUsername());
                    // 参数也放进去
                    savelog.setRequestParams(JSON.toJSONString(loginWithPhoneDTO));
                    log.info("【日志模块-登录日志-手机号验证码登录】：本次请求的参数为{}",JSON.toJSONString(loginWithPhoneDTO));
                }else{
                    // 其余情况，以手机号为操作用户和请求参数
                    String telephone = DesensitizedUtil.mobilePhone((String) arg);
                    savelog.setOperateUser(telephone);
                    savelog.setRequestParams(JSON.toJSONString(telephone));
                    log.info("【日志模块-登录日志-获取短信验证码】：本次请求的参数为{}",JSON.toJSONString(telephone));
                }
            }else{
                // get方法不在考虑之内，不带参数的方法只有一个，退出登陆
                // 按照上述风格输出一个退出登陆的日志
                log.info("【日志模块-登录日志-退出登陆】");
                // 获得当前登陆用户的ID，查询之后保存到日志中
                savelog.setOperateUser(userServiceImpl.getUsernameById(SecurityUtil.getUserId()));
            }
        }else {
            // 操作日志
            savelog.setType(LogTypeEnum.OPERATION.getType());
            savelog.setOperateUser(userServiceImpl.getUsernameById(SecurityUtil.getUserId()));
            // 对操作日志的参数做一点小处理，踩坑
            ArrayList<Object> validArgs = new ArrayList<>();
            // 遍历得到的参数
            for(Object obj : joinPoint.getArgs()){
                // 判断obj的类型，做一些处理
                // 跳过HttpServletResponse，可能导出Excel会报错
                if (obj instanceof HttpServletResponse){
                    continue;
                }
                // 如果参数是MultipartFile类型，请求参数就设置为文件类型名称
                if (obj instanceof MultipartFile file){
                    validArgs.add(file.getClass().getName());
                }
                validArgs.add(obj);
            }
            // 处理完毕之后将参数放入
            // 判断非空
            if (ObjectUtil.isNotEmpty(validArgs)) {
                log.info("【日志模块-操作日志】：本次请求的参数为{}", validArgs);
                savelog.setRequestParams(JSON.toJSONString(validArgs));
            }
        }


        // ==========以上为前置处理的部分，以下为后置处理的部分==========

        Result result;
        // 因为有是否成功的字段，可以加try-catch分开
        try {
            // 获取返回值
            result = (Result) joinPoint.proceed();
            savelog.setResponseData(JSON.toJSONString(result));
            // 设置成功
            savelog.setIsSuccess(RequestStatusEnum.SUCCESS.getStatus());
        }catch (Throwable throwable){

            // 设置失败
            savelog.setIsSuccess(RequestStatusEnum.FAIL.getStatus());
            // 异常信息放到响应结果里
            savelog.setResponseData(throwable.getMessage());
            throw new RuntimeException(throwable);
        }finally {
            // 记录最终时间，计算耗时
            savelog.setRequestTime(System.currentTimeMillis() - startTime);
            logService.save(savelog);
        }
        return result;
    }
}